/**
 * Contains a user entity.
 */
package org.openfk.security.entity;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;

import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.MapKeyManyToMany;
import org.hibernate.validator.Email;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;

import com.sun.istack.NotNull;

/**
 * User entity.
 * @author Sławomir Czerwiński
 * @version 1
 */
@Entity
@Table(name = "users")
public class User implements Serializable {
	/**
	 * Serial version.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * User account state.
	 * @author Sławomir Czerwiński
	 * @version 1
	 */
	public enum State {
		/** User account request. */
		REQUEST,
		/** User account is active. */
		ACTIVE,
		/** User account is temporarily suspended. */
		SUSPENDED,
		/** User account is permanently deleted. */
		DELETED
	}

	/**
	 * User unique and constant identifier.
	 */
	private Long id;

	/**
	 * Gets the user's identifier.
	 * @return The identifier.
	 */
	@Id @GeneratedValue @NotNull
	@Column(updatable = false)
	public Long getId() {
		return this.id;
	}

	/**
	 * Sets the user's identifier.
	 * @param id The identifier.
	 */
	protected void setId(Long id) {
		this.id = id;
	}

	/**
	 * User entity version.
	 */
	@Version @NotNull
	protected Integer version;

	/**
	 * User login.
	 */
	@Length(min = 3, max = 32,
			message = "")
	@NotNull
	protected String login;

	/**
	 * User password.
	 */
	@Length(min = 32, max = 32,
			message = "")
	protected String password;

	/**
	 * User email address.
	 */
	@Length(max = 64,
			message = "")
	@NotEmpty(message = "")
	@Email(message = "")
	protected String email;

	/**
	 * User public name.
	 */
	private String name;

	/**
	 * Gets the user's public name.
	 * @return The public name.
	 */
	@Length(min = 3, max = 64,
			message = "")
	@NotNull
	public String getName() {
		return this.name;
	}

	/**
	 * Sets the user's public name.
	 * @param name The public name.
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * User account state.
	 */
	@Enumerated(EnumType.ORDINAL)
	protected State state;

	@CollectionOfElements(
			targetElement = Role.class)
	@MapKeyManyToMany(
			targetEntity = Application.class,
			joinColumns = @JoinColumn(
					name = "id_application",
					referencedColumnName = "id"))
	@JoinTable(name = "users_applications_roles",
			joinColumns = @JoinColumn(
					name = "id_user",
					referencedColumnName = "id"),
			inverseJoinColumns = @JoinColumn(
					name = "id_role",
					referencedColumnName = "id"))
	protected Map<Application, Role> roles =
		new HashMap<Application, Role>();

	/**
	 * The changes log of the user.
	 */
	@OneToMany(mappedBy = "user")
	protected List<UserLog> logs;

	/**
	 * Accepts the user account request.
	 */
	public void accept() {
		if (this.state == State.REQUEST)
			this.state = State.ACTIVE;
		// TODO: Log
	}

	/**
	 * Rejects the user account request.
	 */
	public void reject() {
		// TODO: Delete record
		// TODO: Log
	}

	/**
	 * Suspends the user account.
	 */
	public void suspend() {
		if (this.state == State.ACTIVE)
			this.state = State.SUSPENDED;
		// TODO: Log
	}

	/**
	 * Restores the suspended user account.
	 */
	public void restore() {
		if (this.state == State.SUSPENDED)
			this.state = State.ACTIVE;
		// TODO: Log
	}

	/**
	 * Deletes the user account.
	 */
	public void delete() {
		if ((this.state == State.ACTIVE) || (this.state == State.SUSPENDED))
			this.state = State.DELETED;
		// TODO: Log
	}

	/**
	 * Grants the role to the user.
	 * @param role The role.
	 */
	public void grant(Role role) {
		// Put the role:
		this.roles.put(role.application, role);
		// TODO: Log
	}

	/**
	 * Revokes the role from the user.
	 * @param role The role.
	 */
	public void revoke(Role role) {
		// Remove if the role in this application equals the role to revoke:
		if (this.roles.get(role.application).equals(role))
			this.roles.remove(role.application);
		// TODO: Log
	}

}
